package morphing;


import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;

public class Warping {

	boolean holdEdges = false;
	
	public Warping(boolean holdEdges) {
		this.holdEdges = holdEdges;
	}
	

	public static double calculaU (int x, int y, int xp, int yp, int xq, int yq) {		
		return ( (x-xp)*(xq-xp) + (y-yp)*(yq-yp) ) /
		(Math.pow(xq-xp,2) + Math.pow(yq-yp,2)) ;	
	}

	public static double calculaV (int x, int y, int xp, int yp, int xq, int yq) {
		return ( (x-xp)*(yq-yp) + (y-yp)*(xp-xq) ) /
		Math.sqrt( Math.pow(xq-xp,2) + Math.pow(yq-yp,2)) ;
	}

	public static int[] calculaXLine(int xp2, int yp2, int xq2, int yq2, double u, double v) {
		double x;
		double y;

		x =  xp2 + u*(xq2-xp2) +  v*(yq2-yp2) / Math.sqrt(Math.pow(xq2-xp2,2) + Math.pow(yq2-yp2,2));
		y = yp2 + u*(yq2-yp2) +  v*(xp2-xq2) / Math.sqrt(Math.pow(xq2-xp2,2) + Math.pow(yq2-yp2,2));

		int[] valor = {(int)Math.round(x), (int)Math.round(y)};

		return valor;		
	}		

	public BufferedImage warping(ArrayList<Line2D.Double> vetor1, ArrayList<Line2D.Double> vetor2, BufferedImage img) {

		if (holdEdges) {
			Line2D.Double bottom_left = 
				new Line2D.Double(0, 0, img.getWidth(), 0);
			Line2D.Double top_left = 
				new Line2D.Double(img.getWidth(), 0, img.getWidth(), img.getHeight());
			Line2D.Double top_right = 
				new Line2D.Double(img.getWidth(), img.getHeight(), 0, img.getHeight());
			Line2D.Double bottom_right = 
				new Line2D.Double(0, img.getHeight(), 0, 0);

			vetor1.add(bottom_left);
			vetor1.add(top_left);
			vetor1.add(top_right);
			vetor1.add(bottom_right);
			
			vetor2.add(bottom_left);
			vetor2.add(top_left);
			vetor2.add(top_right);
			vetor2.add(bottom_right);
		}
		
		int px[] = getArrayXini(vetor1);
		int py[] = getArrayYini(vetor1);
		int qx[] = getArrayXfim(vetor1);
		int qy[] = getArrayYfim(vetor1);
		
		int plx[] = getArrayXini(vetor2);
		int ply[] = getArrayYini(vetor2);
		int qlx[] = getArrayXfim(vetor2);
		int qly[] = getArrayYfim(vetor2);
		
		                                         
		BufferedImage imgProcessada;
		float dxt, dyt, pesot;
		float u, v;
		int[] dx, dy;
		float[]  peso;		
		int[][] ponto = new int[px.length][2]; 
		dx = new int[px.length];
		dy = new int[px.length];	
		peso = new float[px.length];	

		imgProcessada = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());


		for(int alt=0; alt < imgProcessada.getHeight(); alt++)
			for(int larg=0; larg < imgProcessada.getWidth(); larg++) {
				dxt=0; dyt=0; pesot=0;

				for(int i=0;i < px.length; i++) {
					u = (float) calculaU(larg, alt, px[i], py[i], qx[i], qy[i]);
					v = (float) calculaV(larg, alt, px[i], py[i], qx[i], qy[i]);

					ponto[i] = calculaXLine(plx[i], ply[i], qlx[i], qly[i], u, v);

					float dist = Math.abs(v); 

					if (u > 1)
						dist = (int) Math.round(Math.sqrt(Math.pow((larg-qx[i]),2) + Math.pow((alt-qy[i]),2)));
					else if (u < 0)
						dist = (int) Math.round(Math.sqrt(Math.pow((larg-px[i]),2) + Math.pow((alt-py[i]),2)));
					/*
					 * *wi = (li^p / (a + di))^2
					 */
					if (dist < 1)
						dist = 1;
					dx[i]= larg - ponto[i][0]; 
					dy[i]= alt  - ponto[i][1] ;

					/*peso[i] = (float) ((Math.pow((px[i]-qx[i]), 2) + Math.pow(py[i]-qy[i],2))
			                  				/ ((dist + 2)*(dist + 2)));*/
					float li = (float) Math.sqrt(Math.pow(px[i]-qx[i], 2) + Math.pow(py[i]-qy[i],2));
					
					 int p = 1;
					 int a = 0;
					 int b = 2; 
					 
					peso[i] = (float) Math.pow( ( Math.pow(li,p)/(a + dist) ), b);



					dxt += (float) dx[i]*peso[i];
					dyt += (float) dy[i]*peso[i];

					pesot+=peso[i];
				}

				dxt = larg + dxt/pesot;
				dyt = alt  + dyt/pesot;

				if (dxt > 0 && dyt > 0 && dxt < img.getWidth()-1 && dyt < img.getHeight()-1)
					imgProcessada.setRGB(larg, alt, img.getRGB(Math.round(dxt),Math.round(dyt)));		    					
			}
		return imgProcessada;
	}

	public  int[] getArrayXini (ArrayList<Line2D.Double> lista) {
		int[] aux = new int[lista.size()];
		int cont = 0;
		Iterator<Line2D.Double> iter = lista.iterator();
			while(iter.hasNext()){
				aux[cont++]=(int)(iter.next()).x1;
			}
		return aux;
	}
	
	public  int[] getArrayXfim (ArrayList<Line2D.Double> lista) {
		int[] aux = new int[lista.size()];
		int cont = 0;
		Iterator<Line2D.Double> iter = lista.iterator();
			while(iter.hasNext()){
				aux[cont++]=(int)(iter.next()).x2;
			}
		return aux;
	}
	
	public  int[] getArrayYini (ArrayList<Line2D.Double> lista) {
		int[] aux = new int[lista.size()];
		int cont = 0;
		Iterator<Line2D.Double> iter = lista.iterator();
			while(iter.hasNext()){
				aux[cont++]=(int)(iter.next()).y1;
			}
		return aux;
	}
	
	public  int[] getArrayYfim (ArrayList<Line2D.Double> lista) {
		int[] aux = new int[lista.size()];
		int cont = 0;
		Iterator<Line2D.Double> iter = lista.iterator();
			while(iter.hasNext()){
				aux[cont++]=(int)(iter.next()).y2;
			}
		return aux;
	}		
	
}
